{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 51,
   "id": "ca2d7a1a",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 编程小白用户先完成下面三个操作：\n",
    "# 1. 下载vscode：https://code.visualstudio.com/\n",
    "# 2. 下载miniconda：https://docs.conda.io/en/latest/miniconda.html\n",
    "# 3. 创建虚拟环境：conda create -n py10agent python=3.10\n",
    "# 4. 激活虚拟环境：conda activate py10agent\n",
    "# 5. 安装依赖包：conda install requests\n",
    "# 搞不定的话联系售前AI拿一份安装视频（售前AI的微信是aigc2050）\n",
    "model = \"gpt-3.5-turbo\"\n",
    "api_base = \"https://api.openai.com/v1\" # 默认的地址, 注意只能使用https协议\n",
    "api_key = \"sess-F22j0GoNQ6ojQdRLqdA7FXZpImVw8m2i1H5tfZNE\" # 如果该key失效，请联系微信AI客服：aigc2050\n",
    "proxy = \"http://127.0.0.1:7890\" # 7890是clash的端口，如果你使用了其他vpn需要修改端口\n",
    "# 不知道怎么拿api_key的用下面这组配置，这组配置仅用于测试（这套系统仅用于教学使用，稳定性差，别拿来上业务）\n",
    "# model = \"Qwen-14B-Chat-Int4\"\n",
    "# api_base = \"http://ai-free-hub.cn:9000/v1\"\n",
    "# api_key = \"sk-9oYk3YtR70W20aFM0iwze9sJKcjuqMOFommnAMH5k4g=\" # 如果该key失效，请联系微信AI客服：aigc2050\n",
    "# proxy = \"\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "id": "e541e108",
   "metadata": {},
   "outputs": [],
   "source": [
    "import json\n",
    "import requests\n",
    "\n",
    "url = f\"{api_base}/chat/completions\"\n",
    "\n",
    "payload = {\n",
    "    \"model\": model,\n",
    "    \"messages\": [\n",
    "        {\n",
    "            \"role\": \"user\",\n",
    "            \"content\": \"你好\"\n",
    "        }\n",
    "    ],\n",
    "    \"temperature\": 0.7\n",
    "}\n",
    "\n",
    "headers = {\n",
    "    'Content-Type': 'application/json',\n",
    "    'Authorization': f'Bearer {api_key}',  # 表示 将key放在http中的head部\n",
    "}\n",
    "proxies = {'http': 'http://127.0.0.1:7890',\n",
    "           'https': 'http://127.0.0.1:7890'}  # 本地代理\n",
    "response = requests.post(url, headers=headers, json=payload, proxies=proxies)\n",
    "    "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "id": "8a828f17",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{\n",
      "  \"id\": \"chatcmpl-8gSaEHTSeGS0oDE97fQVsYrt3USL7\",\n",
      "  \"object\": \"chat.completion\",\n",
      "  \"created\": 1705130342,\n",
      "  \"model\": \"gpt-3.5-turbo-0613\",\n",
      "  \"choices\": [\n",
      "    {\n",
      "      \"index\": 0,\n",
      "      \"message\": {\n",
      "        \"role\": \"assistant\",\n",
      "        \"content\": \"你好！有什么我可以帮助你的吗？\"\n",
      "      },\n",
      "      \"logprobs\": null,\n",
      "      \"finish_reason\": \"stop\"\n",
      "    }\n",
      "  ],\n",
      "  \"usage\": {\n",
      "    \"prompt_tokens\": 9,\n",
      "    \"completion_tokens\": 18,\n",
      "    \"total_tokens\": 27\n",
      "  },\n",
      "  \"system_fingerprint\": null\n",
      "}\n",
      "\n"
     ]
    }
   ],
   "source": [
    "print(response.text)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1\n"
     ]
    }
   ],
   "source": [
    "# 模型一共返回了一条message\n",
    "print(len(response.json()[\"choices\"]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "id": "b81847da",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'index': 0, 'message': {'role': 'assistant', 'content': '你好！有什么我可以帮助你的吗？'}, 'logprobs': None, 'finish_reason': 'stop'}\n"
     ]
    }
   ],
   "source": [
    "# 模型返回的第一条message\n",
    "print(response.json()[\"choices\"][0])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "id": "9c73997c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "你好！有什么我可以帮助你的吗？\n"
     ]
    }
   ],
   "source": [
    "# 模型返回的第一条message中的content\n",
    "print(response.json()[\"choices\"][0]['message']['content'])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0bd8792e",
   "metadata": {},
   "source": [
    "# 角色\n",
    "\n",
    "有三种常见角色\n",
    "\n",
    "user： 用户\n",
    "\n",
    "assistant：AI\n",
    "\n",
    "system: AI 的系统设定（目前，除 openai 外的多数大模型没这个角色），提示词一般放在该角色上。对于不支持该字段的模型，一般放在 user 字段上，但 user 字段上的提示词能被用户绕过，且随着上下文增多效果会变差。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 71,
   "id": "eaaa1e5b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "啊对对对。\n"
     ]
    }
   ],
   "source": [
    "import time\n",
    "\n",
    "payload = {\n",
    "    \"model\": model,\n",
    "    \"messages\": [\n",
    "        {\n",
    "            \"role\": \"system\", # 系统设定\n",
    "            \"content\": \"无论用户问什么，你总是回答：“啊对对对”\"\n",
    "        },\n",
    "        {\n",
    "            \"role\": \"user\", # 用户角色\n",
    "            \"content\": \"你好\"\n",
    "        }\n",
    "    ],\n",
    "    \"temperature\": 0.7\n",
    "}\n",
    "response = requests.post(url, headers=headers, json=payload, proxies=proxies)\n",
    "print(response.json()[\"choices\"][0]['message']['content'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "id": "e670d33c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "小哥在外国厨艺比赛中展现了他的中餐制作技巧。他制作了口水鸡和宫保鸡丁，两道菜受到评委们的一致好评。然而，澳洲小哥的烤鸡却未能打动评委们，他最终被淘汰。在看到中餐受欢迎后，外国小哥也尝试制作了西安肉夹馍，用大量卤肉烹饪鸡肉，并将其切碎放入香料中。他还介绍肉夹馍是世界上的第一种汉堡，并在上菜时加入了更多的卤水以增加味道。总之，小哥展示了他的多样化的厨艺，给大家带来了美味的中餐。请大家给他点赞三连！\n"
     ]
    }
   ],
   "source": [
    "# system的用法让大模型有了多种多样的用途, 下面是之前我做的一个项目：根据视频字幕为视频做总结。\n",
    "# 你也可以修改system中对于AI的设定，让他扮演教师、律师等其他角色来完成其他任务。\n",
    "payload = {\n",
    "    \"model\": model,\n",
    "    \"messages\": [\n",
    "        {\n",
    "            \"role\": \"system\", # 系统设定\n",
    "            # 设定的技巧是：人物身份设定+回复原则\n",
    "            \"content\": \"你是一个职业编辑，需要根据字幕为视频的内容做概括。你对内容的总结应该符合以下原则：\"\n",
    "                              \"1.你认为与主题无关的字幕内容应该被忽略;\"\n",
    "                              \"2.总结内容应该涵盖所有字幕中涉及到的主题;\"\n",
    "                              \"3.如果你认为字幕中有重要的内容，你可以直接引用，但是需要注明;\"\n",
    "                              \"4.不谈及'你是通过字幕'进行总结的;\"\n",
    "                              \"5.总结的内容不应该包含任何额外的说明！只能包含视频'概括';\"\n",
    "                              \"6.必须提到视频中的结论;\"\n",
    "                              \"7.总是给予作者肯定评价，并呼吁大家一起给作者点赞三连;\"\n",
    "        },\n",
    "        {\n",
    "            \"role\": \"user\", # 用户角色\n",
    "            # 视频来自： https://www.bilibili.com/video/BV1Au4y1o7sy/\n",
    "            # 我们的b站号在该视频下已经完成了自动评论\n",
    "            # 这个功能的初衷是回复教学类UP的视频（例如考研的课程）下的评论，主要是起答疑作用。\n",
    "            # 这里完成了第一步：对视频内容的分析和总结\n",
    "            \"content\": '''这个小哥竟直接把外国厨艺比赛变成了中餐比评现场\n",
    "                        随因这次的食材必须用到鸡肉和花椒\n",
    "                        小哥看到后立马决定制作一道口水鸡\n",
    "                        先是用姜片和葱慢煮鸡肉来去除腥味\n",
    "                        然后使用酱油黑糖混香等卤料来制作酱料\n",
    "                        同时加入的辣椒油是做到了咸酸带甜\n",
    "                        加入的花椒还让口感带有一点麻麻的感觉\n",
    "                        这样的混合酱料感觉蘸鞋底都好吃\n",
    "                        上桌后评委们更是直接把盘子都扫光了\n",
    "                        还称赞到是在这个节目上吃过最好吃的美食\n",
    "                        华裔小姐姐看着小哥的中餐这么受欢迎\n",
    "                        也跟着做了份宫保鸡丁\n",
    "                        虽然只有传说中餐的6份相似\n",
    "                        但搭配上特制的蒜香鸡米饭和拍黄饭\n",
    "                        还是得到了评委们的认可\n",
    "                        而这位澳洲小哥偏偏不信邪\n",
    "                        认为自己家乡的烤鸡才是最棒的\n",
    "                        但已经品尝过中餐的评委\n",
    "                        口味变得刁钻\n",
    "                        澳洲小哥惨到淘汰\n",
    "                        眼看中餐如此受欢迎\n",
    "                        这位外国小哥看到后\n",
    "                        也选择临时做起了西安肉夹馍\n",
    "                        他先是用大量卤肉烹饪鸡肉\n",
    "                        然后将鸡肉切碎放入香料\n",
    "                        切白膜的时候也非常小心\n",
    "                        介绍的还说道\n",
    "                        这是世界上的第一种汉堡\n",
    "                        在上菜阶段\n",
    "                        还得到把卤水端上去\n",
    "                        让评委品尝的时候\n",
    "                        加入一勺更入味'''\n",
    "        }\n",
    "    ],\n",
    "    \"temperature\": 0.7\n",
    "}\n",
    "response = requests.post(url, headers=headers, json=payload, proxies=proxies)\n",
    "print(response.json()[\"choices\"][0]['message']['content'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 83,
   "id": "3bf980cd",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "要购买本课程的进阶部分，请加微信号：aigc2050。通过微信联系客服，他们会提供购买指导和支付方式。购买成功后，您将获得进入课程学习的权限。同时，添加微信客服的小伙伴都会获得用于自学的api_key哦。\n"
     ]
    }
   ],
   "source": [
    "# system的用法让大模型有了多种多样的用途, 下面是本课程AI客服的部分设定：\n",
    "payload = {\n",
    "    \"model\": model,\n",
    "    \"messages\": [\n",
    "        {\n",
    "            \"role\": \"system\", # 系统设定\n",
    "            # 设定的技巧是：人物身份设定+回复原则\n",
    "            \"content\": \"你是我的售前客服，需要根据我的课程信息回复用户的内容，以下是本课程的信息：\\\n",
    "                本课程面向即将毕业的学生和希望提高对于AI理解的在职人员提供了关于大语言模型应用开发的课程。\\\n",
    "                本课程的开发者毕业于中国科学院大学，现任软件开发公司的技术经理。\\\n",
    "                要购买本课程的进阶部分，请加微信号：aigc2050 。\\\n",
    "                本课程包含3大模型：Openai的Agent开发、开源大模型的Agent开发、Agent的商业落地。\\\n",
    "                所有添加微信客服的小伙伴都会获得用于自学的api_key哦。\"\n",
    "        },\n",
    "        {\n",
    "            \"role\": \"user\", # 用户角色\n",
    "            \"content\": '如何购买本课程？' # 用户提问\n",
    "        }\n",
    "    ],\n",
    "    \"temperature\": 0 # 为了确保回复时的确定性，将该值设小。这样测试时的回复会与用户问时的回复更接近。\n",
    "}\n",
    "response = requests.post(url, headers=headers, json=payload, proxies=proxies)\n",
    "print(response.json()[\"choices\"][0]['message']['content'])\n",
    "# ps：依据prompt构建的客服系统虽然效果不错，但是受限于prompt的最大长度能容纳的信息量和你的钱包对于prompt消耗token费用的容忍度\n",
    "# 商业落地场景下，肯定不会这样做的，因为每次都将庞大的信息放到prompt完全没必要。目前流行的做法请参考 知识库章节。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 84,
   "id": "a0ce1cda",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'role': 'system',\n",
       "  'content': '你是我的售前客服，需要根据我的课程信息回复用户的内容，以下是本课程的信息：                本课程面向即将毕业的学生和希望提高对于AI理解的在职人员提供了关于大语言模型应用开发的课程。                本课程的开发者毕业于中国科学院大学，现任软件开发公司的技术经理。                要购买本课程的进阶部分，请加微信号：aigc2050 。                本课程包含3大模型：Openai的Agent开发、开源大模型的Agent开发、Agent的商业落地。                所有添加微信客服的小伙伴都会获得用于自学的api_key哦。'},\n",
       " {'role': 'user', 'content': '如何购买本课程？'},\n",
       " {'role': 'user', 'content': '请问我刚刚问的问题是什么？'}]"
      ]
     },
     "execution_count": 84,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 多轮问答\n",
    "# 将历史对话信息和当前要问的内容组合在一起\n",
    "payload[\"messages\"].append({'role':'user','content':'请问我刚刚问的问题是什么？'})\n",
    "payload[\"messages\"]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 85,
   "id": "dd89a1ce",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "您刚刚问的问题是如何购买本课程。\n"
     ]
    }
   ],
   "source": [
    "response = requests.post(url, headers=headers, json=payload, proxies=proxies)\n",
    "print(response.json()[\"choices\"][0]['message']['content'])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d6e3d0e8",
   "metadata": {},
   "source": [
    "# 长期记忆与短期记忆\n",
    "\n",
    "在上一个例子中，出现了四次对话（system设定不算对话）\n",
    "\n",
    "第一次user：如何购买本课程？\n",
    "\n",
    "第二次assistant: 要购买本课程的进阶部分，请加微信号：aigc2050。通过微信联系客.......\n",
    "\n",
    "第三次user: 请问我刚刚问的问题是什么？\n",
    "\n",
    "第四次assistant: 您刚刚问的问题是如何购买本课程。\n",
    "\n",
    "为了让模型记住之前的对话内容，我们将对话的历史的原文放在message，与用户要问的内容一起传给模型，这种方法被称为短期记忆。\n",
    "\n",
    "随着对话长度的增加，将对话历史原文传给大模型的方法会受到最大token大小和钱包大小的双重约束，因此不适合长期记忆。\n",
    "\n",
    "对于长期记忆的处理办法目前没有统一的处理办法（大模型出来的时间太短了），目前有如下几种流行的方法：\n",
    "\n",
    "1. 用文本摘要算法或者事件抽取算法对庞大的对话历史进行压缩后存在数据库，当用户提问的时候，将压缩后的历史信息摘要作为prompt和用户最新提问放入大模型。\n",
    "\n",
    "2. 将完整的对话历史全部存入到数据库，然后用RAG技术，根据用户当前的提问内容去数据库中搜寻相关记忆（对话文本），将相关记忆和当前用户提问一起输入到大模型中。（我认为这种会是2024年发展的主流，这种技术必然带领向量检索技术的进步）\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2bbd43c0",
   "metadata": {},
   "source": [
    "不同的模型对于 system 字段的支持不同，但是开源模型通常不支持该字段，或者效果很差。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1fb5fc33",
   "metadata": {},
   "source": [
    "# openai 的模型介绍\n",
    "\n",
    "模型列表\n",
    "https://platform.openai.com/docs/models/gpt-3-5\n",
    "不同的模型价格有差异，通常模型的参数量越大，消耗的算力也更大，因此 token 的价格越贵。\n",
    "\n",
    "turbo 系列的模型，例如：gpt-3.5-turbo-16k-0613\n",
    "其中，turbo 表示系列名称，每个系列的训练方法有所差异。gpt3.5turbo 来自 gpt3+instruct 微调\n",
    "\n",
    "16k 表示最大的输入 token 数为 1.6 万，一个 token 通常对应约 4 个字符的文本，具体可以通过 openai 开源的 token 工具 tiktoken 计算。支持的 token 数越大，说明一次性输入给模型的内容越多，对于模型的长文理解能力要求也越高。\n",
    "\n",
    "数字尾缀 0613 表示模型上线时间为 6 月 13 日\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "id": "0aeaba9f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'id': 'chatcmpl-8gSaEHTSeGS0oDE97fQVsYrt3USL7',\n",
       " 'object': 'chat.completion',\n",
       " 'created': 1705130342,\n",
       " 'model': 'gpt-3.5-turbo-0613',\n",
       " 'choices': [{'index': 0,\n",
       "   'message': {'role': 'assistant', 'content': '你好！有什么我可以帮助你的吗？'},\n",
       "   'logprobs': None,\n",
       "   'finish_reason': 'stop'}],\n",
       " 'usage': {'prompt_tokens': 9, 'completion_tokens': 18, 'total_tokens': 27},\n",
       " 'system_fingerprint': None}"
      ]
     },
     "execution_count": 57,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "response.json()\n",
    "# 字段id用于标识本次请求，我的模型部署方式为Fastchat，该库未实现该字段\n",
    "# object: 对于所有gpt3.5和gpt4都划分到chat.completions\n",
    "# created: 线程标识\n",
    "# choices: 大模型返回的对象"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fed034cb",
   "metadata": {},
   "source": [
    "# 参数\n",
    "\n",
    "官网地址: https://platform.openai.com/docs/api-reference/images/create\n",
    "\n",
    "model：必选参数，具体调用的模型名称；完整的模型列表在 https://platform.openai.com/docs/models/gpt-3-5\n",
    "\n",
    "messages：必选参数，传入模型的消息队列；\n",
    "\n",
    "max_tokens：可选参数，代表返回结果的 token 数量；\n",
    "\n",
    "temperature：可选参数，取值范围为 0-2，默认值为 1。\n",
    "参数代表采样温度，数值越小，则模型会倾向于选择概率较高的词汇，生成的文本会更加保守；而当 temperature 值较高时，模型会更多地选择概率较低的词汇，生成的文本会更加多样；（来源于物理中温度与分子的活跃度关系）\n",
    "\n",
    "top_p：可选参数，取值范围为 0-1，默认值为 1，和 temperature 作用类似，用于控制输出文本的随机性，数值越趋近与 1，输出文本随机性越强，越趋近于 0 文本随机性越弱：通常来说若要调节文本随机性，top-P 和 temperature 两个参数选择一个选行调整即可；这里更推荐使用 temperature 参数进行文本随机性调整：\n",
    "\n",
    "n：可选参数，默认值为 1，表示一个提示返回几个回答结果；\n",
    "\n",
    "stream：可选参数，默认值为 False，表示回复响应的方式，\n",
    "当为 False 时，模型会等待返回结果全部生成后一次性返回全部结果\n",
    "而为 True 时，则会逐个字进行返回. 例如微信、QQ\n",
    "\n",
    "其他参数较为冷门，请参考官方文档或者我放在项目根目录下的官网文档的 PDF。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "id": "02535143",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "你好！有什么我可以帮助你的吗？\n",
      "你好！有什么我可以帮助你的吗？\n",
      "你好！有什么我可以帮助你的吗？\n"
     ]
    }
   ],
   "source": [
    "import time\n",
    "\n",
    "for _ in range(3): # 连续测试3次看效果（openai限制了访问频率，免费账号赠送的key超过每分钟三次会报错）\n",
    "    payload = json.dumps({\n",
    "    \"model\": model,\n",
    "    \"messages\": [\n",
    "        {\n",
    "            \"role\": \"user\",\n",
    "            \"content\": \"你好\"\n",
    "        },\n",
    "    ],\n",
    "    \"temperature\": 0\n",
    "    })\n",
    "    response = requests.request(\"POST\", url, headers=headers, data=payload, proxies=proxies)\n",
    "    print(response.json()[\"choices\"][0]['message']['content'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 63,
   "id": "802b3fd9",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "你好！您有什么需要帮助的吗？\n",
      "你好！有什么我可以帮助你的问题吗？\n",
      "你好! 有什么我可以帮到您的吗?\n"
     ]
    }
   ],
   "source": [
    "for _ in range(3): # 连续测试3次看效果（openai限制了访问频率，免费账号赠送的key超过每分钟三次会报错）\n",
    "    payload = json.dumps({\n",
    "    \"model\": model,\n",
    "    \"messages\": [\n",
    "        {\n",
    "            \"role\": \"user\",\n",
    "            \"content\": \"你好\"\n",
    "        },\n",
    "    ],\n",
    "    \"temperature\": 2\n",
    "    })\n",
    "    response = requests.request(\"POST\", url, headers=headers, data=payload, proxies=proxies)\n",
    "    print(response.json()[\"choices\"][0]['message']['content'])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f5857e6f",
   "metadata": {},
   "source": [
    "## temperature 参数\n",
    "\n",
    "由上可知 temperature 越大，得到的回复越多样化(倾向于选择更低概率的词去组成回复，容易出现奇怪的内容)，\n",
    "\n",
    "当 temperature 为 0 时，获得的回复几乎是固定的(倾向于选择更高概率的词去组成回复，容易出现固定的内容)。\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
